package gr.aueb.cs.mscis.chordroid.tracker;

import gr.aueb.cs.mscis.chordroid.chord.NodeIdentifier;
import gr.aueb.cs.mscis.chordroid.chord.ProtocolType;
import gr.aueb.cs.mscis.chordroid.chord.RemoteMessage;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;

/**
 * This class implements the tracker required for the bootstrapping
 * of our system. Each new node through a BootstrapService instance
 * communicates with the tracker in order to fetch a NodeIdentifier
 * instance of a node already in the chord system.
 * @author lupin
 *
 */
public class ChordTracker implements Runnable {
	private Set<TrackerRecord> setOfNodes = new HashSet<TrackerRecord>();
	private int TcpPort = 6666;
	private ObjectInputStream ois;
	private ObjectOutputStream oos;
	private ServerSocket welcomeSocket;


	/**
	 * This method constructs the RemoteMessage to be sent
	 * back to the client. It fetches a randomly selected
	 * TrackerRecord and returns the NodeIdentifier of that
	 * record to the client. In case the records' list is
	 * empty the tracker returns a create message so that
	 * a new chord will be created 
	 * @param client
	 * @return
	 * @throws UnknownHostException
	 */
	private RemoteMessage checkForNodes(Socket client) throws UnknownHostException {

		TrackerRecord tr = new TrackerRecord(client.getInetAddress(),5001);

		if (setOfNodes.contains(tr)) {
			tr.setPort(5002);
		}

		System.out.println(tr);
		
		if (!setOfNodes.isEmpty()) {
			TrackerRecord returnRecord= null;
			
			int randomNodeId = (int) Math.round(Math.random() * setOfNodes.size());
			System.out.println("id: " + randomNodeId);

			TrackerRecord randomNode = setOfNodes.iterator().next();
//			try {
//				if (randomNode.getAddr().isReachable(3000)) {
					returnRecord = randomNode;
//				} else {
//					setOfNodes.remove(randomNodeId);
//					return checkForNodes(client);
//				}			
//			} catch (IOException e) {
//				e.printStackTrace();
//			}
			setOfNodes.add(tr);
			return new RemoteMessage(ProtocolType.JOIN_CHORD,
					new NodeIdentifier(returnRecord.getAddr(), returnRecord
							.getPort()), new NodeIdentifier(tr.getAddr(), tr
							.getPort()));
		} else {
			setOfNodes.add(tr);
			return new RemoteMessage(ProtocolType.CREATE_CHORD, null, new NodeIdentifier(tr.getAddr(),tr.getPort()));
		}
	}

	public void run() {
		try {
			System.out.println("Waiting...");
			welcomeSocket = new ServerSocket(TcpPort);
			for (;;) {
				Socket inFromClient = welcomeSocket.accept();
				ois = new ObjectInputStream(inFromClient.getInputStream());
				RemoteMessage clientMsg = (RemoteMessage) ois.readObject();
				System.out.println("client msg: " + clientMsg.getProtocolType().toString());
				if (clientMsg.getProtocolType().equals(ProtocolType.REGISTER)) {
					RemoteMessage msg = checkForNodes(inFromClient);
					sendReply(inFromClient, msg);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
 			e.printStackTrace();
		}
	}

	/**
	 * This method sends the reply RemoteMessage back to the client
	 * @param connectionSocket
	 * @param replyMsg
	 */
	private void sendReply(Socket connectionSocket, RemoteMessage replyMsg) {
		try {
			oos = new ObjectOutputStream(connectionSocket.getOutputStream());
			oos.writeObject(replyMsg);
			oos.flush();
			System.out.println("Reply-type-sent: " + replyMsg.getProtocolType().toString());
			connectionSocket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * The instance of the tracker runs on a server on a well known
	 * address and port
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread(new ChordTracker());
		t.start();
	}

}